using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NSubstitute;
using Xunit.Abstractions;
using Xunit.Internal;
using Xunit.Runner.Common;
using Xunit.Runner.v2;
using Xunit.Sdk;

// This is just a copy of the v2 Mocks for now, until we can sever v2 ties from the v3 code.

namespace Xunit.v3
{
	public static class Mocks
	{
		public static IAssemblyInfo AssemblyInfo(
			ITypeInfo[]? types = null,
			IReflectionAttributeInfo[]? attributes = null,
			string? assemblyFileName = null)
		{
			attributes = attributes ?? new IReflectionAttributeInfo[0];

			var result = Substitute.For<IAssemblyInfo, InterfaceProxy<IAssemblyInfo>>();
			result.Name.Returns(assemblyFileName == null ? "assembly:" + Guid.NewGuid().ToString("n") : Path.GetFileNameWithoutExtension(assemblyFileName));
			result.AssemblyPath.Returns(assemblyFileName);
			result.GetType("").ReturnsForAnyArgs(types?.FirstOrDefault());
			result.GetTypes(true).ReturnsForAnyArgs(types ?? new ITypeInfo[0]);
			result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
			return result;
		}

		public static IReflectionAttributeInfo CollectionAttribute(string collectionName)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new CollectionAttribute(collectionName));
			result.GetConstructorArguments().Returns(new[] { collectionName });
			return result;
		}

		public static IReflectionAttributeInfo CollectionBehaviorAttribute(
			CollectionBehavior? collectionBehavior = null,
			bool disableTestParallelization = false,
			int maxParallelThreads = 0)
		{
			CollectionBehaviorAttribute attribute;
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();

			if (collectionBehavior.HasValue)
			{
				attribute = new CollectionBehaviorAttribute(collectionBehavior.GetValueOrDefault());
				result.GetConstructorArguments().Returns(new object[] { collectionBehavior });
			}
			else
			{
				attribute = new CollectionBehaviorAttribute();
				result.GetConstructorArguments().Returns(new object[0]);
			}

			attribute.DisableTestParallelization = disableTestParallelization;
			attribute.MaxParallelThreads = maxParallelThreads;

			result.Attribute.Returns(attribute);
			result.GetNamedArgument<bool>("DisableTestParallelization").Returns(disableTestParallelization);
			result.GetNamedArgument<int>("MaxParallelThreads").Returns(maxParallelThreads);
			return result;
		}

		public static IReflectionAttributeInfo CollectionBehaviorAttribute(
			Type factoryType,
			bool disableTestParallelization = false,
			int maxParallelThreads = 0)
		{
			var attribute = new CollectionBehaviorAttribute(factoryType)
			{
				DisableTestParallelization = disableTestParallelization,
				MaxParallelThreads = maxParallelThreads
			};
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(attribute);
			result.GetNamedArgument<bool>("DisableTestParallelization").Returns(disableTestParallelization);
			result.GetNamedArgument<int>("MaxParallelThreads").Returns(maxParallelThreads);
			result.GetConstructorArguments().Returns(new object[] { factoryType });
			return result;
		}

		public static IReflectionAttributeInfo CollectionBehaviorAttribute(
			string factoryTypeName,
			string factoryAssemblyName,
			bool disableTestParallelization = false,
			int maxParallelThreads = 0)
		{
			var attribute = new CollectionBehaviorAttribute(factoryTypeName, factoryAssemblyName)
			{
				DisableTestParallelization = disableTestParallelization,
				MaxParallelThreads = maxParallelThreads
			};
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(attribute);
			result.GetNamedArgument<bool>("DisableTestParallelization").Returns(disableTestParallelization);
			result.GetNamedArgument<int>("MaxParallelThreads").Returns(maxParallelThreads);
			result.GetConstructorArguments().Returns(new object[] { factoryTypeName, factoryAssemblyName });
			return result;
		}

		public static IReflectionAttributeInfo CollectionDefinitionAttribute(string collectionName)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new CollectionDefinitionAttribute(collectionName));
			result.GetConstructorArguments().Returns(new[] { collectionName });
			return result;
		}

		public static IReflectionAttributeInfo DataAttribute(IEnumerable<object[]>? data = null)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			var dataAttribute = Substitute.For<DataAttribute>();
			dataAttribute.GetData(null!).ReturnsForAnyArgs(data);
			result.Attribute.Returns(dataAttribute);
			return result;
		}

		public static ExecutionErrorTestCase ExecutionErrorTestCase(
			string message,
			_IMessageSink? diagnosticMessageSink = null)
		{
			var testMethod = TestMethod();
			return new ExecutionErrorTestCase(
				diagnosticMessageSink ?? new _NullMessageSink(),
				TestMethodDisplay.ClassAndMethod,
				TestMethodDisplayOptions.None,
				testMethod,
				message
			);
		}

		public static IReflectionAttributeInfo FactAttribute(
			string? displayName = null,
			string? skip = null,
			int timeout = 0)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new FactAttribute { DisplayName = displayName, Skip = skip, Timeout = timeout });
			result.GetNamedArgument<string>("DisplayName").Returns(displayName);
			result.GetNamedArgument<string>("Skip").Returns(skip);
			result.GetNamedArgument<int>("Timeout").Returns(timeout);
			return result;
		}

		static IEnumerable<IAttributeInfo> LookupAttribute(
			string fullyQualifiedTypeName,
			IReflectionAttributeInfo[]? attributes)
		{
			if (attributes == null)
				return Enumerable.Empty<IAttributeInfo>();

			var attributeType = Type.GetType(fullyQualifiedTypeName);
			if (attributeType == null)
				return Enumerable.Empty<IAttributeInfo>();

			return attributes.Where(attribute => attributeType.IsAssignableFrom(attribute.Attribute.GetType())).ToList();
		}

		public static IMethodInfo MethodInfo(
			string? methodName = null,
			IReflectionAttributeInfo[]? attributes = null,
			IParameterInfo[]? parameters = null,
			ITypeInfo? type = null,
			ITypeInfo? returnType = null,
			bool isAbstract = false,
			bool isPublic = true,
			bool isStatic = false)
		{
			var result = Substitute.For<IMethodInfo, InterfaceProxy<IMethodInfo>>();

			attributes ??= new IReflectionAttributeInfo[0];
			parameters ??= new IParameterInfo[0];

			result.IsAbstract.Returns(isAbstract);
			result.IsPublic.Returns(isPublic);
			result.IsStatic.Returns(isStatic);
			result.Name.Returns(methodName ?? "method:" + Guid.NewGuid().ToString("n"));
			result.ReturnType.Returns(returnType);
			result.Type.Returns(type);
			result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
			result.GetParameters().Returns(parameters);

			return result;
		}

		public static IParameterInfo ParameterInfo(string name)
		{
			var result = Substitute.For<IParameterInfo, InterfaceProxy<IParameterInfo>>();
			result.Name.Returns(name);
			return result;
		}

		public static IReflectionMethodInfo ReflectionMethodInfo<TClass>(string methodName)
		{
			return Reflector.Wrap(typeof(TClass).GetMethod(methodName)!);
		}

		public static IReflectionTypeInfo ReflectionTypeInfo<TClass>()
		{
			return Reflector.Wrap(typeof(TClass));
		}

		public static IRunnerReporter RunnerReporter(
			string? runnerSwitch = null,
			string? description = null,
			bool isEnvironmentallyEnabled = false,
			_IMessageSink? messageSink = null)
		{
			var result = Substitute.For<IRunnerReporter, InterfaceProxy<IRunnerReporter>>();
			result.Description.Returns(description ?? "The runner reporter description");
			result.IsEnvironmentallyEnabled.ReturnsForAnyArgs(isEnvironmentallyEnabled);
			result.RunnerSwitch.Returns(runnerSwitch);
			messageSink ??= Substitute.For<_IMessageSink, InterfaceProxy<_IMessageSink>>();
			result.CreateMessageHandler(null!).ReturnsForAnyArgs(messageSink);
			return result;
		}

		public static ITest Test(
			ITestCase testCase,
			string displayName)
		{
			var result = Substitute.For<ITest, InterfaceProxy<ITest>>();
			result.DisplayName.Returns(displayName);
			result.TestCase.Returns(testCase);
			return result;
		}

		public static ITestAssembly TestAssembly(IReflectionAttributeInfo[] attributes)
		{
			var assemblyInfo = AssemblyInfo(attributes: attributes);

			var result = Substitute.For<ITestAssembly, InterfaceProxy<ITestAssembly>>();
			result.Assembly.Returns(assemblyInfo);
			return result;
		}

		public static ITestAssembly TestAssembly(
			string assemblyFileName,
			string? configFileName = null,
			ITypeInfo[]? types = null,
			IReflectionAttributeInfo[]? attributes = null)
		{
			var assemblyInfo = AssemblyInfo(types, attributes, assemblyFileName);

			var result = Substitute.For<ITestAssembly, InterfaceProxy<ITestAssembly>>();
			result.Assembly.Returns(assemblyInfo);
			result.ConfigFileName.Returns(configFileName);
			return result;
		}

		public static TestAssembly TestAssembly(
			Assembly? assembly = null,
			string? configFileName = null)
		{
#if NETFRAMEWORK
			if (configFileName == null)
				configFileName = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
#endif

			return new TestAssembly(Reflector.Wrap(assembly ?? typeof(Mocks).Assembly), configFileName);
		}

		public static ITestAssemblyDiscoveryFinished TestAssemblyDiscoveryFinished(
			bool diagnosticMessages = false,
			int toRun = 42,
			int discovered = 2112)
		{
			var assembly = new XunitProjectAssembly { AssemblyFilename = "testAssembly.dll", ConfigFilename = "testAssembly.dll.config" };
			var config = new TestAssemblyConfiguration { DiagnosticMessages = diagnosticMessages, ShadowCopy = true };
			var result = Substitute.For<ITestAssemblyDiscoveryFinished, InterfaceProxy<ITestAssemblyDiscoveryFinished>>();
			result.Assembly.Returns(assembly);
			result.DiscoveryOptions.Returns(_TestFrameworkOptions.ForDiscovery(config));
			result.TestCasesDiscovered.Returns(discovered);
			result.TestCasesToRun.Returns(toRun);
			return result;
		}

		public static ITestAssemblyDiscoveryStarting TestAssemblyDiscoveryStarting(
			bool diagnosticMessages = false,
			AppDomainOption appDomain = AppDomainOption.Disabled,
			bool shadowCopy = false)
		{
			var assembly = new XunitProjectAssembly { AssemblyFilename = "testAssembly.dll", ConfigFilename = "testAssembly.dll.config" };
			var config = new TestAssemblyConfiguration { DiagnosticMessages = diagnosticMessages, MethodDisplay = TestMethodDisplay.ClassAndMethod, MaxParallelThreads = 42, ParallelizeTestCollections = true, ShadowCopy = shadowCopy };
			var result = Substitute.For<ITestAssemblyDiscoveryStarting, InterfaceProxy<ITestAssemblyDiscoveryStarting>>();
			result.AppDomain.Returns(appDomain);
			result.Assembly.Returns(assembly);
			result.DiscoveryOptions.Returns(_TestFrameworkOptions.ForDiscovery(config));
			result.ShadowCopy.Returns(shadowCopy);
			return result;
		}

		public static ITestAssemblyExecutionFinished TestAssemblyExecutionFinished(
			bool diagnosticMessages = false,
			int total = 2112,
			int failed = 42,
			int skipped = 8,
			int errors = 6,
			decimal time = 123.456M)
		{
			var assembly = new XunitProjectAssembly { AssemblyFilename = "testAssembly.dll", ConfigFilename = "testAssembly.dll.config" };
			var config = new TestAssemblyConfiguration { DiagnosticMessages = diagnosticMessages, ShadowCopy = true };
			var summary = new ExecutionSummary { Total = total, Failed = failed, Skipped = skipped, Errors = errors, Time = time };
			var result = Substitute.For<ITestAssemblyExecutionFinished, InterfaceProxy<ITestAssemblyExecutionFinished>>();
			result.Assembly.Returns(assembly);
			result.ExecutionOptions.Returns(_TestFrameworkOptions.ForExecution(config));
			result.ExecutionSummary.Returns(summary);
			return result;
		}

		public static ITestAssemblyExecutionStarting TestAssemblyExecutionStarting(
			bool diagnosticMessages = false,
			string? assemblyFilename = null)
		{
			var assembly = new XunitProjectAssembly { AssemblyFilename = assemblyFilename ?? "testAssembly.dll", ConfigFilename = "testAssembly.dll.config" };
			var config = new TestAssemblyConfiguration { DiagnosticMessages = diagnosticMessages, MethodDisplay = TestMethodDisplay.ClassAndMethod, MaxParallelThreads = 42, ParallelizeTestCollections = true, ShadowCopy = true };
			var result = Substitute.For<ITestAssemblyExecutionStarting, InterfaceProxy<ITestAssemblyExecutionStarting>>();
			result.Assembly.Returns(assembly);
			result.ExecutionOptions.Returns(_TestFrameworkOptions.ForExecution(config));
			return result;
		}

		public static _TestAssemblyFinished TestAssemblyFinished(
			string assemblyUniqueID = "asm-unique-id",
			int testsRun = 2112,
			int testsFailed = 42,
			int testsSkipped = 6,
			decimal executionTime = 123.4567M) =>
				new _TestAssemblyFinished
				{
					AssemblyUniqueID = assemblyUniqueID,
					ExecutionTime = executionTime,
					TestsFailed = testsFailed,
					TestsRun = testsRun,
					TestsSkipped = testsSkipped
				};

		public static ITestCase TestCase(ITestCollection? collection = null)
		{
			if (collection == null)
				collection = TestCollection();

			var result = Substitute.For<ITestCase, InterfaceProxy<ITestCase>>();
			result.TestMethod.TestClass.TestCollection.Returns(collection);
			return result;
		}

		public static ITestCase TestCase(ITestMethod testMethod)
		{
			var result = Substitute.For<ITestCase, InterfaceProxy<ITestCase>>();
			result.TestMethod.Returns(testMethod);
			return result;
		}

		public static ITestCase TestCase<TClassUnderTest>(
			string methodName,
			string? displayName = null,
			string? skipReason = null,
			string? uniqueID = null,
			string? fileName = null,
			int? lineNumber = null)
		{
			return TestCase(typeof(TClassUnderTest), methodName, displayName, skipReason, uniqueID, fileName, lineNumber);
		}

		public static ITestCase TestCase(
			Type type,
			string methodName,
			string? displayName = null,
			string? skipReason = null,
			string? uniqueID = null,
			string? fileName = null,
			int? lineNumber = null)
		{
			var testMethod = TestMethod(type, methodName);
			var traits = GetTraits(testMethod.Method);

			var result = Substitute.For<ITestCase, InterfaceProxy<ITestCase>>();
			result.DisplayName.Returns(displayName ?? $"{type}.{methodName}");
			result.SkipReason.Returns(skipReason);
			result.TestMethod.Returns(testMethod);
			result.Traits.Returns(traits);
			result.UniqueID.Returns(uniqueID ?? Guid.NewGuid().ToString());

			if (fileName != null && lineNumber != null)
			{
				var sourceInfo = new SourceInformation { FileName = fileName, LineNumber = lineNumber };
				result.SourceInformation.Returns(sourceInfo);
			}

			return result;
		}

		public static IReflectionAttributeInfo TestCaseOrdererAttribute(string typeName, string assemblyName)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TestCaseOrdererAttribute(typeName, assemblyName));
			result.GetConstructorArguments().Returns(new object[] { typeName, assemblyName });
			return result;
		}

		public static IReflectionAttributeInfo TestCaseOrdererAttribute(Type type)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TestCaseOrdererAttribute(type));
			result.GetConstructorArguments().Returns(new object[] { type });
			return result;
		}

		public static IReflectionAttributeInfo TestCaseOrdererAttribute<TOrderer>() =>
			TestCaseOrdererAttribute(typeof(TOrderer));

		public static ITestClass TestClass(
			string? typeName = null,
			IReflectionAttributeInfo[]? attributes = null)
		{
			var testCollection = TestCollection();
			var typeInfo = TypeInfo(typeName, attributes: attributes);

			var result = Substitute.For<ITestClass, InterfaceProxy<ITestClass>>();
			result.Class.Returns(typeInfo);
			result.TestCollection.Returns(testCollection);
			return result;
		}

		public static TestClass TestClass(
			Type type,
			ITestCollection? collection = null)
		{
			if (collection == null)
				collection = TestCollection(type.Assembly);

			return new TestClass(collection, Reflector.Wrap(type));
		}

		public static TestCollection TestCollection(
			Assembly? assembly = null,
			ITypeInfo? definition = null,
			string? displayName = null)
		{
			if (assembly == null)
				assembly = typeof(Mocks).Assembly;
			if (displayName == null)
				displayName = "Mock test collection";

			return new TestCollection(TestAssembly(assembly), definition, displayName);
		}

		public static _TestCollectionFinished TestCollectionFinished(
			int testsRun = 0,
			int testsFailed = 0,
			int testsSkipped = 0,
			decimal executionTime = 0m,
			string testCollectionUniqueID = "<unset>") =>
				new _TestCollectionFinished
				{
					ExecutionTime = executionTime,
					TestCollectionUniqueID = testCollectionUniqueID,
					TestsFailed = testsFailed,
					TestsRun = testsRun,
					TestsSkipped = testsSkipped
				};

		public static _TestCollectionStarting TestCollectionStarting(
			string? testCollectionClass = null,
			string testCollectionDisplayName = "<unset>",
			string testCollectionUniqueID = "<unset>") =>
				new _TestCollectionStarting
				{
					TestCollectionClass = testCollectionClass,
					TestCollectionDisplayName = testCollectionDisplayName,
					TestCollectionUniqueID = testCollectionUniqueID
				};

		public static IReflectionAttributeInfo TestCollectionOrdererAttribute(string typeName, string assemblyName)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TestCollectionOrdererAttribute(typeName, assemblyName));
			result.GetConstructorArguments().Returns(new object[] { typeName, assemblyName });
			return result;
		}

		public static IReflectionAttributeInfo TestCollectionOrdererAttribute(Type type)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TestCollectionOrdererAttribute(type));
			result.GetConstructorArguments().Returns(new object[] { type });
			return result;
		}

		public static IReflectionAttributeInfo TestCollectionOrdererAttribute<TOrderer>() =>
			TestCollectionOrdererAttribute(typeof(TOrderer));

		public static ITestFailed TestFailed(
			Type type,
			string methodName,
			string? displayName = null,
			string? output = null,
			decimal executionTime = 0M,
			Exception? ex = null)
		{
			var testCase = TestCase(type, methodName);
			var test = Test(testCase, displayName ?? "NO DISPLAY NAME");
			var failureInfo = ExceptionUtility.ConvertExceptionToFailureInformation(ex ?? new Exception());

			var result = Substitute.For<ITestFailed, InterfaceProxy<ITestFailed>>();
			result.ExceptionParentIndices.Returns(failureInfo.ExceptionParentIndices);
			result.ExceptionTypes.Returns(failureInfo.ExceptionTypes);
			result.ExecutionTime.Returns(executionTime);
			result.Messages.Returns(failureInfo.Messages);
			result.Output.Returns(output);
			result.StackTraces.Returns(failureInfo.StackTraces);
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			return result;
		}

		public static ITestFailed TestFailed(
			string displayName,
			decimal executionTime,
			string? exceptionType = null,
			string? exceptionMessage = null,
			string? stackTrace = null,
			string? output = null)
		{
			var testCase = TestCase();
			var test = Test(testCase, displayName);
			var result = Substitute.For<ITestFailed, InterfaceProxy<ITestFailed>>();
			result.ExceptionParentIndices.Returns(new[] { -1 });
			result.ExceptionTypes.Returns(new[] { exceptionType });
			result.ExecutionTime.Returns(executionTime);
			result.Messages.Returns(new[] { exceptionMessage });
			result.Output.Returns(output);
			result.StackTraces.Returns(new[] { stackTrace });
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			return result;
		}

		public static IReflectionAttributeInfo TestFrameworkAttribute(Type type)
		{
			var attribute = Activator.CreateInstance(type);
			if (attribute == null)
				throw new InvalidOperationException($"Unable to create attribute instance: '{type.FullName}'");

			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(attribute);
			result.GetCustomAttributes(null).ReturnsForAnyArgs(
				callInfo => LookupAttribute(
					callInfo.Arg<string>(),
					CustomAttributeData.GetCustomAttributes(attribute.GetType()).Select(x => Reflector.Wrap(x)).ToArray()
				)
			);
			return result;
		}

		public static ITestMethod TestMethod(
			string? typeName = null,
			string? methodName = null,
			string? displayName = null,
			string? skip = null,
			int timeout = 0,
			IEnumerable<IParameterInfo>? parameters = null,
			IEnumerable<IReflectionAttributeInfo>? classAttributes = null,
			IEnumerable<IReflectionAttributeInfo>? methodAttributes = null)
		{
			if (classAttributes == null)
				classAttributes = Enumerable.Empty<IReflectionAttributeInfo>();
			if (methodAttributes == null)
				methodAttributes = Enumerable.Empty<IReflectionAttributeInfo>();
			if (parameters == null)
				parameters = Enumerable.Empty<IParameterInfo>();

			var factAttribute = methodAttributes.FirstOrDefault(attr => typeof(FactAttribute).IsAssignableFrom(attr.Attribute.GetType()));
			if (factAttribute == null)
			{
				factAttribute = FactAttribute(displayName, skip, timeout);
				methodAttributes = methodAttributes.Concat(new[] { factAttribute });
			}

			var testClass = TestClass(typeName, attributes: classAttributes.ToArray());
			var methodInfo = MethodInfo(methodName, methodAttributes.ToArray(), parameters.ToArray(), testClass.Class);

			var result = Substitute.For<ITestMethod, InterfaceProxy<ITestMethod>>();
			result.Method.Returns(methodInfo);
			result.TestClass.Returns(testClass);
			return result;
		}

		public static TestMethod TestMethod(
			Type type,
			string methodName,
			ITestCollection? collection = null)
		{
			var @class = TestClass(type, collection);
			var methodInfo = type.GetMethod(methodName);
			if (methodInfo == null)
				throw new Exception($"Unknown method: {type.FullName}.{methodName}");

			return new TestMethod(@class, Reflector.Wrap(methodInfo));
		}

		public static ITestPassed TestPassed(
			Type type,
			string methodName,
			string? displayName = null,
			string? output = null,
			decimal executionTime = 0M)
		{
			var testCase = TestCase(type, methodName);
			var test = Test(testCase, displayName ?? "NO DISPLAY NAME");

			var result = Substitute.For<ITestPassed, InterfaceProxy<ITestPassed>>();
			result.ExecutionTime.Returns(executionTime);
			result.Output.Returns(output);
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			return result;
		}

		public static ITestPassed TestPassed(
			string displayName,
			string? output = null)
		{
			var testCase = TestCase();
			var test = Test(testCase, displayName);
			var result = Substitute.For<ITestPassed, InterfaceProxy<ITestPassed>>();
			result.Test.Returns(test);
			result.ExecutionTime.Returns(1.2345M);
			result.Output.Returns(output);
			return result;
		}

		public static ITestResultMessage TestResult<TClassUnderTest>(
			string methodName,
			string displayName,
			decimal executionTime)
		{
			var testCase = TestCase<TClassUnderTest>(methodName);
			var test = Test(testCase, displayName);
			var result = Substitute.For<ITestResultMessage, InterfaceProxy<ITestResultMessage>>();
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			result.ExecutionTime.Returns(executionTime);
			return result;
		}

		public static ITestSkipped TestSkipped(
			Type type,
			string methodName,
			string? displayName = null,
			string? output = null,
			decimal executionTime = 0M,
			string? skipReason = null)
		{
			var testCase = TestCase(type, methodName);
			var test = Test(testCase, displayName ?? "NO DISPLAY NAME");

			var result = Substitute.For<ITestSkipped, InterfaceProxy<ITestSkipped>>();
			result.ExecutionTime.Returns(executionTime);
			result.Output.Returns(output);
			result.Reason.Returns(skipReason);
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			return result;
		}

		public static ITestSkipped TestSkipped(
			string displayName,
			string? skipReason = null)
		{
			var testCase = TestCase();
			var test = Test(testCase, displayName);

			var result = Substitute.For<ITestSkipped, InterfaceProxy<ITestSkipped>>();
			result.Reason.Returns(skipReason);
			result.TestCase.Returns(testCase);
			result.Test.Returns(test);
			return result;
		}

		public static ITestStarting TestStarting(string displayName)
		{
			var testCase = TestCase();
			var test = Test(testCase, displayName);
			var result = Substitute.For<ITestStarting, InterfaceProxy<ITestStarting>>();
			result.Test.Returns(test);
			return result;
		}

		public static IReflectionAttributeInfo TheoryAttribute(
			string? displayName = null,
			string? skip = null,
			int timeout = 0)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TheoryAttribute { DisplayName = displayName, Skip = skip });
			result.GetNamedArgument<string>("DisplayName").Returns(displayName);
			result.GetNamedArgument<string>("Skip").Returns(skip);
			result.GetNamedArgument<int>("Timeout").Returns(timeout);
			return result;
		}

		public static IReflectionAttributeInfo TraitAttribute<T>()
			where T : Attribute, new()
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new T());
			return result;
		}

		public static IReflectionAttributeInfo TraitAttribute(
			string name,
			string value)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			var traitDiscovererAttributes = new[] { TraitDiscovererAttribute() };
			result.GetCustomAttributes(typeof(TraitDiscovererAttribute)).Returns(traitDiscovererAttributes);
			result.Attribute.Returns(new TraitAttribute(name, value));
			result.GetConstructorArguments().Returns(new object[] { name, value });
			return result;
		}

		public static IAttributeInfo TraitDiscovererAttribute(
			string typeName = "Xunit.Sdk.TraitDiscoverer",
			string assemblyName = "xunit.v3.core")
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TraitDiscovererAttribute(typeName, assemblyName));
			result.GetConstructorArguments().Returns(new object[] { typeName, assemblyName });
			return result;
		}

		public static IAttributeInfo TraitDiscovererAttribute(Type discovererType)
		{
			var result = Substitute.For<IReflectionAttributeInfo, InterfaceProxy<IReflectionAttributeInfo>>();
			result.Attribute.Returns(new TraitDiscovererAttribute(discovererType));
			result.GetConstructorArguments().Returns(new object[] { discovererType });
			return result;
		}

		public static IAttributeInfo TraitDiscovererAttribute<TDiscoverer>() =>
			TraitDiscovererAttribute(typeof(TDiscoverer));

		public static ITypeInfo TypeInfo(
			string? typeName = null,
			IMethodInfo[]? methods = null,
			IReflectionAttributeInfo[]? attributes = null,
			string? assemblyFileName = null)
		{
			var result = Substitute.For<ITypeInfo, InterfaceProxy<ITypeInfo>>();
			result.Name.Returns(typeName ?? "type:" + Guid.NewGuid().ToString("n"));
			result.GetMethods(false).ReturnsForAnyArgs(methods ?? new IMethodInfo[0]);
			var assemblyInfo = AssemblyInfo(assemblyFileName: assemblyFileName);
			result.Assembly.Returns(assemblyInfo);
			result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
			return result;
		}

		public static XunitTestCase XunitTestCase<TClassUnderTest>(
			string methodName,
			ITestCollection? collection = null,
			object[]? testMethodArguments = null,
			_IMessageSink? diagnosticMessageSink = null)
		{
			var method = TestMethod(typeof(TClassUnderTest), methodName, collection);

			return new XunitTestCase(diagnosticMessageSink ?? new _NullMessageSink(), TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, method, testMethodArguments);
		}

		public static XunitTheoryTestCase XunitTheoryTestCase<TClassUnderTest>(
			string methodName,
			ITestCollection? collection = null,
			_IMessageSink? diagnosticMessageSink = null)
		{
			var method = TestMethod(typeof(TClassUnderTest), methodName, collection);

			return new XunitTheoryTestCase(diagnosticMessageSink ?? new _NullMessageSink(), TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, method);
		}

		// Helpers

		static Dictionary<string, List<string>> GetTraits(IMethodInfo method)
		{
			var result = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

			foreach (var traitAttribute in method.GetCustomAttributes(typeof(TraitAttribute)))
			{
				var ctorArgs = traitAttribute.GetConstructorArguments().ToList();
				result.Add((string)ctorArgs[0], (string)ctorArgs[1]);
			}

			return result;
		}
	}
}
